# coding: utf-8
#### 使用层的概念，以及误差反向传播法来实现神经网路的学习

import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict


class TwoLayerNet:

    # 输入层神经元的数量， 隐藏层神经元的数量， 输出层神经元的数量， 初始化权重时高斯分布的规模
    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 初始化权重
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.rand(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)  # 偏置使用元素0填充满
        self.params['W2'] = weight_init_std * np.random.rand(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)

        # 生成层
        self.layers = OrderedDict()  # 层，将使用有序字典放置，毕竟层的顺序很重要
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])

        self.lastLayer = SoftmaxWithLoss()  # 最后一层，单独一个参数

    # 神经网络处理，在这里，它会一直处理到输出，但只到输出
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)  # 简洁的处理，专注于局部，局部专注好之后，全局自然ok

        return x  # 这里准确的来讲是y

    # 求损失函数
    # x:输入数据， t:监督数据
    def loss(self, x, t):
        y = self.predict(x)  # 先求解输出
        return self.lastLayer.forward(y, t)  # 这里是把两步合成一步来，第一步是将输出通过softmax激活函数正规化，第二步是将正规化后的结果和正解标签通过交叉熵误差求解损失函数值

    # 求识别精确度
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)  # 在列的方向上，取出推理的答案
        if t.ndim != 1 : t = np.argmax(t, axis=1)  # 如果监督数据为one-hot-vector形式

        accuracy = np.sum(y == t) / float(x.shape[0])  # 到了这里的时候，y和t都是一维数组了
        return accuracy

    # 求解梯度: 数值微分求解梯度
    # x:输入数据， t:监督数据
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)  # 简单的新建一个函数，更为直观的连接权重参数和损失函数值

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])  # 暂时不要纠结到很细的细节
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])  # 暂时不要纠结到很细的细节
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])  # 暂时不要纠结很小的细节
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])  # 暂时不要纠结很小的细节

        return grads

    #  误差反向传播法求解梯度，用层
    def gradient(self, x, t):
        # forward
        self.loss(x, t)

        # backward
        # 先从最后一层反向传
        dout = 1
        dout = self.lastLayer.backward(dout)

        # 然后逐层反着往回传
        layers = list(self.layers.values())  # 先拿到所有层
        layers.reverse()  # 翻转所有层
        #  for循环之后，所有的反向传播都已经完成，所有的梯度值也都拿到了（在层里面存着）
        for layer in layers:
            dout = layer.backward(dout)  # 再回去看看原来写的层，人都是计划好了的

        # 设定（我说是拿梯度值）
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads






